package moa.learners.reservoir;

import java.io.FileNotFoundException;
import java.util.HashSet;

import moa.learners.reservoir.AbstractBlackList;
import moa.learners.reservoir.SlicedBlackList;
import moa.learners.reservoir.AbstractSlides;
import moa.learners.reservoir.IndexedSlides;
import moa.learners.reservoir.AbstractWriter;
import moa.learners.reservoir.DiskWriter;

import com.espertech.esper.client.*;
import com.espertech.esper.collection.Pair;/*
import com.espertech.esperio.AdapterInputSource;
import com.espertech.esperio.csv.CSVInputAdapter;
import com.espertech.esperio.csv.CSVInputAdapterSpec; */


/**
 * By default, the Reservoir state is saved every 1000 events
 */
public class Reservoir {//implements com.espertech.esper.client.UpdateListener {

	private AbstractSlides<HashSet<String>> tanker;
	private AbstractBlackList blacklist;
	private AbstractWriter<HashSet<String>> writer;

	private long countEvents = 0;
	private boolean saveEveryNEvents = true;
	private long savingPeriod = 1000;

	/**
	 * This method computes the Reservoir's capacity based on given error thresholds
	 * 
	 * @param epsilon	ε error threshold
	 * @param delta		δ error threshold
	 * @param winSize	sliding window size
	 * @param fileID	name of the source file
	 */
	public Reservoir(double epsilon, double delta, int winSize, String fileID) {
		if( epsilon <= 0 || epsilon >= 1)
			throw new IllegalArgumentException( "First parameter must be in [0,1[");

		if( delta <= 0 || delta >= 1)
			throw new IllegalArgumentException( "Second parameter must be in [0,1[");

		int cardSd = (int) Math.ceil(Math.log(2/delta) / (2*epsilon*epsilon));

		tanker = new IndexedSlides<HashSet<String>>(cardSd, winSize);

		blacklist = new SlicedBlackList(winSize);

		writer = new DiskWriter<HashSet<String>>( "./test" + fileID + ".arff");
	}

	/**
	 * This method uses directly the given Reservoir's capacity 
	 * 
	 * @param cardSd	Reservoir's capacity
	 * @param winSize	sliding window size
	 * @param fileID	name of the source file
	 */
	public Reservoir( int cardSd, int winSize, String fileID) {
		if( cardSd < 2)
			throw new IllegalArgumentException( "First parameter must be > 1");

		if( winSize < 1)
			throw new IllegalArgumentException( "Second parameter must be > 0");

		tanker = new IndexedSlides<HashSet<String>>(cardSd, winSize);

		blacklist = new SlicedBlackList(winSize);

		writer = new DiskWriter<HashSet<String>>( "./test" + fileID + ".arff");
	}

	/**
	 * @param saveEveryNEvents yes means that the saving time will be based on the number of events processed,
	 * 							false that is has to be based on an external timer
	 */
	public void setPeriodicSave( boolean saveEveryNEvents) {
		this.saveEveryNEvents = saveEveryNEvents;
	}

	/**
	 * @param savingPeriod period between each save, expressed in number of events
	 */
	public void setSavingPeriod(long savingPeriod) {
		this.savingPeriod = savingPeriod;
	}

	/**
	 * @return the current period between each save, expressed in number of events
	 */
	public long getSavingPeriod() {
		return savingPeriod;
	}

	/**
	 * Determines whether it is time for saving the current state of the Reservoir
	 * 
	 * @return true if the Reservoir contents can be dumped after the given time, false otherwise
	 */
	private boolean isTimeToWrite() {
		if(saveEveryNEvents && ++countEvents == savingPeriod) {
			countEvents = 0;
			return true;
		}
		else
			return false;
	}

	public void update(String seqId, String itemSetList){//EventBean[] newEvents) {
		//EventBean event = newEvents[0];

		String seqNumber = seqId ;//event.get("sequenceID").toString();

		// we don't do anything if the sequence is blacklisted
		if( ! blacklist.contains(seqNumber))
		{
			// we store the transaction's items in a HashSet
			String[] tabItemSet = itemSetList.split("-1");//event.get("itemset").toString().split(",");
			HashSet<String> hashedItemSet = new HashSet<String>();
			for( String item : tabItemSet)
				hashedItemSet.add(item);

			// is the sequence already stored?
			if( tanker.hasSequenceNumber(seqNumber)) {
				// yes, we add the new itemset to the window
				// does the window slide?
				if( tanker.addAndSlide( seqNumber, hashedItemSet))
					// yes, we decrement each blacklisted sequence number's counter
					blacklist.decrement();
			}
			else {
				// as long as the reservoir is not full, we just add sequences
				if(tanker.getNbSequences() == tanker.getMaxSequences())
				{
					// no, we retrieve a random number in the range [0,maxSequences[ (total number of sequences 
					int loto = (int) Math.floor(Math.random() * tanker.getMaxSequences());

					// we replace the old sequence by a new one, retrieve its ID and number of elements in the window
					Pair<String, Integer> removedSequence = tanker.replace( loto, seqNumber, hashedItemSet);
					blacklist.add(removedSequence.getFirst(), removedSequence.getSecond());
				}
				else
					tanker.addSequence(seqNumber, hashedItemSet);
			}
		}
		
		if( isTimeToWrite()) {
			try {
				//writer.Write(tanker);
				writer.WriteSeq(tanker);
			} catch (Exception e) {
				System.err.println("Couldn't save the actual state of Reservoir");
				e.printStackTrace();
			}
		}
	}




	///////////////////////////////////////////////////////////////////////////////////////////////////////


	public static void inf(String errMsg) {
		System.out.println(errMsg);
		System.out.println();
		System.out.println("Parameters: Epsilon Delta WindowSize File");
		System.out.println("\tEpsilon E ]0,1[");
		System.out.println("\tDelta E ]0,1[");
		System.out.println("\tWindowSize > 1");
	}

	public static void main(String[] args) throws FileNotFoundException {

		double epsilon = new Double((float)0.5); // TO CHOOSE
		if( epsilon <= 0 || epsilon >= 1) {
			inf("Wrong value for Epsilon");
			return;
		}

		double delta = new Double((float)0.5);// TO CHOOSE
		if( delta <= 0 || delta >= 1) {
			inf("Wrong value for Delta");
			return;
		}

		int cardSd = (int) Math.ceil(Math.log(2/delta) / (2*epsilon*epsilon));
		int winSize = new Integer( 2); // TO CHOOSE

		System.out.println( "Starting with " + winSize + " itemsets for " + cardSd + " sequences");
		Reservoir dogs = new Reservoir(cardSd, winSize, "result.arff"); // TO CHOOSE
		// To know when we will store the data
		dogs.setSavingPeriod(3);
		
		dogs.update("2", "2 6 -1"); 
		dogs.update("1", "1 2 3 -1 5 6 8 10");
		dogs.update("2", "1 2 3 -1 5 6 8");
		dogs.update("3", "1 2 3 -1 5 6 8");
		
	}
}
